package com.airport.schedule.greedy;

import com.airport.schedule.ScheduleSolution;
import com.airport.schedule.model.Flight;
import com.airport.schedule.model.FlightGate;
import com.airport.schedule.util.ListUtil;
import com.airport.schedule.util.MapUtil;
import com.airport.schedule.util.SetUtil;
import edu.stanford.nlp.util.IntervalTree;

import java.util.*;

public class GreedyAdHocScheduler {

    private ScheduleSolution solution;
    private List<Flight> noRegularGateFlights;
    private Random random;
    private List<Flight> devilFlights;
    private Map<Flight,Double> flight2ConflictRate;

    public GreedyAdHocScheduler(ScheduleSolution solution, Collection<Flight> noRegularGateFlights) {
        this.random = new Random(System.currentTimeMillis());
        this.solution = solution;
        this.noRegularGateFlights = new ArrayList<>(noRegularGateFlights);
        this.devilFlights = new ArrayList<>();
        this.flight2ConflictRate = new HashMap<>();
        IntervalTree<Integer, Flight> intervalTree = new IntervalTree<>();
        intervalTree.addAll(noRegularGateFlights);
        noRegularGateFlights.forEach(iFlight->{
            Set<FlightGate> iGates = new HashSet<>(solution.getAvailableAdHocFlightGates(iFlight));
            List<Flight> conflictFlights = intervalTree.getOverlapping(iFlight);
            conflictFlights.remove(iFlight);
            flight2ConflictRate.put(iFlight, 0.0);
            conflictFlights.forEach(jFlight->{
                Set<FlightGate> jGates = new HashSet<>(solution.getAvailableAdHocFlightGates(jFlight));
                Set<FlightGate> common = SetUtil.intersect(iGates, jGates);
                flight2ConflictRate.put(iFlight, flight2ConflictRate.get(iFlight)
                        + (common.size()+0.0)/jGates.size());
            });
        });
    }

    public List<Flight> getDevilFlights() {
        return devilFlights;
    }

    public void greedyProcess() {
        // 旅客数多的优先安排
        Collections.sort(noRegularGateFlights, (f1, f2)->Double.compare(flight2ConflictRate.get(f1),
                flight2ConflictRate.get(f2)));

        while(!noRegularGateFlights.isEmpty()) {
            Flight flight = noRegularGateFlights.remove(0);
            List<FlightGate> gates = solution.getAvailableAdHocFlightGates(flight);
            if(!gates.isEmpty()) {
                if(gates.size() == 1) {
                    FlightGate gate = gates.get(0);
                    if(!canAssignCurrentGate(flight, gate, noRegularGateFlights, solution)) {
                        solution.remove(flight, gate);
                        devilFlights.add(flight);
                    }
                }else{
                    FlightGate gate = findBestFlightGate(gates,solution,flight,noRegularGateFlights);
                    solution.assign(flight, gate);
                }
            }else{
                devilFlights.add(flight);
            }
        }
    }

    private boolean canAssignCurrentGate(Flight flight, FlightGate currentGate,
                                         List<Flight> flights, ScheduleSolution solution) {
        int currentNoFlightGateNum = getNoFlightGateNum(solution, flights);
        solution.assign(flight, currentGate);
        int noFlightGateNum = getNoFlightGateNum(solution, flights);
        return noFlightGateNum - currentNoFlightGateNum <= 1;
    }

    private int getNoFlightGateNum(ScheduleSolution sol, List<Flight> restFlight) {
        int num = 0;
        for(Flight flight : restFlight) {
            if(sol.getAvailableAdHocFlightGates(flight).isEmpty()) {
                num++;
            }
        }
        return num;
    }

    public FlightGate findBestFlightGate(List<FlightGate> candidates, ScheduleSolution solution,
                                  Flight curFlight, List<Flight> unassignedFlights) {
        Map<FlightGate, Double> gateOccupiedRate = new HashMap<>();
        Map<FlightGate, Double> gateRunwayConflictRate = new HashMap<>();
        for(FlightGate g : candidates) {
            gateOccupiedRate.put(g, 0.0);
            gateRunwayConflictRate.put(g, 0.0);
        }
        Set<FlightGate> curFlightGates = new HashSet<>(candidates);
        for(Flight comingFlight : unassignedFlights) {
            Set<FlightGate> comingFlightGates =
                    new HashSet<>(solution.getAvailableAdHocFlightGates(comingFlight));
            if(SetUtil.hasIntersect(curFlightGates, comingFlightGates)) {
                for(FlightGate gate : curFlightGates) {
                    if(comingFlightGates.contains(gate)) {
                        gateOccupiedRate.put(gate, gateOccupiedRate.get(gate)+1.0/comingFlightGates.size());
                    }
                }
            }
        }
        for(FlightGate gate : candidates) {
            solution.assign(curFlight, gate);
            int conflictNum = solution.getRunwayConflictNum();
            solution.remove(curFlight, gate);
            gateRunwayConflictRate.put(gate, gateRunwayConflictRate.get(gate)+conflictNum);
        }
        MapUtil.normalize(gateOccupiedRate);
        MapUtil.normalize(gateRunwayConflictRate);
        double minVal = Double.MAX_VALUE;
        double gamma = 0.95;
        List<FlightGate> bestGates = new ArrayList<>();
        for(Map.Entry<FlightGate, Double> entry : gateOccupiedRate.entrySet()) {
            double value = (1 - gamma) * entry.getValue() + gamma * gateRunwayConflictRate.get(entry.getKey());
            if(value  < minVal) {
                if(value == minVal) {
                    bestGates.add(entry.getKey());
                }else{
                    bestGates = new ArrayList<>();
                    bestGates.add(entry.getKey());
                }
                minVal = value;
            }
        }
        return bestGates.get(random.nextInt(bestGates.size()));
    }

}
